﻿using System;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Xml;


namespace BusinessLogic.Cryptography
{

	public class RSADigitalSignatureManager
	{

		/// <summary>
		/// Generates RSA keys pair
		/// </summary>
		/// <returns>A pair of randomly generated public/private keys </returns>
		public static RSAKeys GenerateRSAKeys()
		{
			RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider();

			RSAParameters rsaParameters = rsaProvider.ExportParameters(true);

			RSAKeys keys = new RSAKeys();

			keys.PublicKey = rsaProvider.ToXmlString(false);
			keys.PrivateKey = rsaProvider.ToXmlString(true);

			return keys;
		}


		/// <summary>
		/// Signs the XML document
		/// </summary>
		/// <param name="xmlFile">string in XML format to sing</param>
		/// <param name="privateKey">private key to sign the XML file with with</param>
		/// <returns>XML document signature (in XML format)</returns>
		public static string SignXmlFile(string xmlFile, string privateKey)
		{
			RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider();
			rsaProvider.FromXmlString(privateKey);

			// xmlFile must be checked first for validity
			// assuming it's perfectly fine.
			XmlDocument xmlDocument = new XmlDocument();
			xmlDocument.LoadXml(xmlFile);

			// removing the ns attributes from the root element (patch)
			xmlDocument.DocumentElement.RemoveAttribute("xmlns:xsi");
			xmlDocument.DocumentElement.RemoveAttribute("xmlns:xsd");


			SignedXml signedXml = new SignedXml(xmlDocument);
			signedXml.SigningKey = rsaProvider;

			Reference reference = new Reference();
			reference.Uri = "";

			XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
			reference.AddTransform(env);

			signedXml.AddReference(reference);

			signedXml.ComputeSignature();

			XmlElement signature = signedXml.GetXml();

			return signature.OuterXml;
		}


		/// <summary>
		/// Verifies the RSA digital signature of the document in XML format
		/// </summary>
		/// <param name="xmlFile">XML document</param>
		/// <param name="signature">signature of the XML document (also in XML format)</param>
		/// <param name="publicKey">public key that corresponds to the private one the document has been signed with.</param>
		/// <returns>true - if the verification passes, false - otherwise</returns>
		public static bool VerifySginature(string xmlFile, string signature, string publicKey)
		{
			// importing the public key to RSA provider
			RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider();
			rsaProvider.FromXmlString(publicKey);

			XmlDocument xmlDocument = new XmlDocument();
			xmlDocument.LoadXml(xmlFile);

			// removing the ns attributes from the root element (patch)
			xmlDocument.DocumentElement.RemoveAttribute("xmlns:xsi");
			xmlDocument.DocumentElement.RemoveAttribute("xmlns:xsd");

			SignedXml signedXml = new SignedXml(xmlDocument);

			XmlDocument xmlSignature = new XmlDocument();
			xmlSignature.LoadXml(signature);
			signedXml.LoadXml(xmlSignature.DocumentElement);

			return signedXml.CheckSignature(rsaProvider);
		}


		/// <summary>
		/// Saves the RSA key to the file
		/// In reality private keys are usually stored on specialized devices.
		/// In our case we simply serialize it to XML and save on the HD to be later
		/// used by the client
		/// </summary>
		/// <param name="rsaKey"></param>
		/// <param name="xmlFileName"></param>
		public static void SaveRsaKey(string rsaKey, string xmlFileName)
		{
			var xmlDocument = new XmlDocument();
			xmlDocument.LoadXml(rsaKey);
			xmlDocument.Save(xmlFileName);
		}


		/// <summary>
		/// Basic usage of RSADigitalSignatureManager.
		/// Shouldn't be there...
		/// </summary>
		public static void DigitalSigningTest()
		{
			string testXmlDocument = 
@"<?xml version=""1.0"" encoding=""utf-16""?>
<RemittanceAdvice>
  <ID>0</ID>
  <Number>11117898</Number>
  <PayerPartyAccount>
    <ID>0</ID>
    <Accountee>
      <ID>0</ID>
    </Accountee>
    <Currency>
      <ID>0</ID>
    </Currency>
    <Number>11223344</Number>
    <Balance>1000</Balance>
  </PayerPartyAccount>
  <BillToPartyAccount>
    <ID>0</ID>
    <Accountee>
      <ID>0</ID>
    </Accountee>
    <Number>12341234</Number>
    <Balance>0</Balance>
  </BillToPartyAccount>
  <Currency>
    <ID>0</ID>
    <Name>Белоруссий рубль</Name>
    <Code>BYR</Code>
  </Currency>
  <Status>Sent</Status>
  <Description>asfsfdsfs</Description>
  <TotalAmount>500</TotalAmount>
  <IssueDate>2010-12-28T18:28:11</IssueDate>
</RemittanceAdvice>";


			RSAKeys keys = RSADigitalSignatureManager.GenerateRSAKeys();

			string digitalSignature = RSADigitalSignatureManager.SignXmlFile(testXmlDocument, keys.PrivateKey);

			RSAKeys anotherPairOfKeys = RSADigitalSignatureManager.GenerateRSAKeys();

			Debug.Assert(RSADigitalSignatureManager.VerifySginature(testXmlDocument, digitalSignature, keys.PublicKey));
			Debug.Assert(!RSADigitalSignatureManager.VerifySginature(testXmlDocument, digitalSignature, anotherPairOfKeys.PublicKey));

			SaveRsaKey(keys.PublicKey, "public");
			SaveRsaKey(keys.PrivateKey, "private");
		}



	}
}
